package p30_异常处理;

import cn.hutool.core.collection.ListUtil;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;

import java.io.IOException;

/**
 * RequestMappingHandlerAdapter在调用控制器方法过程中(doDispatch方法内部)，如果发生了异常，就会捕获到异常，并且赋值给一个doDispatch方法的局部变量dispatchException
 * 随后执行processDispatchResult方法，并把HandlerAdapter返回值(模型视图mv对象)、dispatchException和一些其他对象作为方法参数
 * 在processDispatchResult方法里：
 *    1.如果发现异常变量不为空，就会根据异常类型去回调对应的异常处理方法(扫描到的异常处理器封装成HandlerExceptionResolver)
 *    2.异常处理方法可以写在控制器里面，此时的异常处理方法只能增强本控制器内发生的异常
 */
public class _ExceptionResolver {
    public static void main(String[] args) throws Exception {
        //ExceptionHandlerExceptionResolver是HandlerExceptionResolver实现类
        ExceptionHandlerExceptionResolver resolver = new ExceptionHandlerExceptionResolver();
        //异常处理方法也有参数，也可以使用参数解析器，返回值处理器，和消息转换器
        resolver.setMessageConverters(ListUtil.of(new MappingJackson2HttpMessageConverter()));
        //添加默认的参数解析器和返回值处理器
        resolver.afterPropertiesSet();

        /**
         * 1.测试json
         * 返回值处理器会解析异常处理方法的返回值，这里会将异常处理方法的返回值用消息转换器处理后放入响应体
         */
        MockHttpServletRequest request1  = new MockHttpServletRequest();
        MockHttpServletResponse response1 = new MockHttpServletResponse();
        HandlerMethod handlerMethod1 = new HandlerMethod(new MyController(), MyController.class.getMethod("foo"));
        //这里直接创建一个异常，来模拟异常被捕获了
        Exception e1 = new ArithmeticException("被零除啦...");
        /**
         * 异常处理器知道异常类型，异常在哪个类被触发，以及请求响应对象
         * 它会拿着异常对象去抛出异常的控制器类内部寻找有没有一个@ExceptionHandler方法，找到了就拿着异常类型与方法参数类型匹配
         * 没找到就去@ControllerAdvice寻找全局@ExceptionHandler方法，看有没有一个方法参数与异常类型匹配
         * 一旦找到，就执行方法(参数解析器会给方法参数赋值)，然后根据返回类型去找一个返回值处理器来处理返回值
         */
        resolver.resolveException(request1, response1, handlerMethod1, e1);
        System.out.println(new String(response1.getContentAsByteArray()));

        /**
         * 2.测试mav
         * 返回值处理器会解析异常处理方法的返回值，这里会将异常处理方法的返回值作为模型数据放入ModelAndView
         */
        MockHttpServletRequest request2  = new MockHttpServletRequest();
        MockHttpServletResponse response2 = new MockHttpServletResponse();
        HandlerMethod handlerMethod2 = new HandlerMethod(new MyController(), MyController.class.getMethod("foo"));
        Exception e2 = new NullPointerException("空指针啦...");
        //返回ModelAndView不为空，则走视图解析流程
        ModelAndView mav = resolver.resolveException(request2, response2, handlerMethod2, e2);
        //异常模型数据
        System.out.println(mav.getModel());
        //视图名称
        System.out.println(mav.getViewName());

        /**
         * 3.异常嵌套
         * e3是Exception，但是能被处理ArithmeticException类型的异常处理方法捕获
         * 因为resolver.resolveException里面，会判断异常对象里面有没有嵌套异常，有的话会递归将嵌套异常变成异常数组
         * 然后开始逐一寻找异常处理方法，找到就停下并使用该处理方法
         */
        MockHttpServletRequest request3  = new MockHttpServletRequest();
        MockHttpServletResponse response3 = new MockHttpServletResponse();
        HandlerMethod handlerMethod3 = new HandlerMethod(new MyController(), MyController.class.getMethod("foo"));
        IOException e3 = new IOException("e1", new ClassNotFoundException("e2", new ArithmeticException("e3")));
        //从MyController中找有没有对应异常处理方法
        resolver.resolveException(request3, response3, handlerMethod3, e3);
        System.out.println(new String(response3.getContentAsByteArray()));

        /**
         * ExceptionHandlerExceptionResolver能够重用参数解析器，返回值处理器，实现组件重用
         * 能够支持嵌套异常
         */
    }
}
